/* 
 * Depends:
 *	jquery-1.4.2.js
 *	tvb-utils.js
 */
TVB.util.namespace('TVB.canvas');
/*
** canvas color conversion
*/
(function(){
    var ZERO     = "0",
        isArray  = function () {},
        isNumber = function () {};
		
	TVB.canvas = function(el, options){
		this.init(el, options);
	};
	TVB.canvas.prototype = {	
		el: null,
		ctx: null,
		color: TVB.util.colortrans,
		width: 500, 
		height: 360, 
		bgcolor: '#FFF',
		_options: {},
		_unit: '%', // percent, all
		_labels: {
			month:['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
		},
		labels: [],
		
		_getAlignByCircleAngle: function(angle){
			if(angle < 90){
				return 'left';
			}else if(angle < 180){
				return 'right';
			}else if(angle < 270){
				return 'right';
			}else{
				return 'left';
			}
		},
		_colors: function(){
			var base = [0,210,100,270,60,240,90,170,240,300,150,330];
			var arr = [], column, hue, color;
			for (var i = 0; i < base.length; i += 1) {
				hue = base[i];
				column = {};
				color = this.hsv2rgb(hue, 0.7, 0.6);
				arr.push('rgb('+color.join(',')+')');
				//color = Color.rgb2hex(color[0], color[1], color[2]);
				//arr.push('#'+color);
			}
			return arr;
		},
		__colors: function(i){
			var hue = Math.floor(i/6)*10 + i%6*60;
			var color = this.hsv2rgb(hue, 0.7, 0.6);
			return 'rgb('+color.join(',')+')';
		},
		init: function(el, options){
			var el = $(el);
			if (!el.length || !el[0].getContext) {
				return false;
			}
			this.el = el;
			this.ctx = el[0].getContext("2d");
			for(var i in options){
				this[i] = options[i];
			}
			this._options = options;
			if(this._options.width){
				this.width = this._options.width;
			}else{
				this.width = el.width();
			}
			if(this._options.height){
				this.height = this._options.height;
			}else{
				this.height = el.height();
			}
			this.ctx.fillStyle = this.bgcolor;
			this.ctx.fillRect (0, 0, el.width(), el.height());
			if(options.labels){
				this.labels = this._labels[options.labels] ? this._labels[options.labels] : options.labels;
			}else{
				this.labels = this._labels['month'];
			}
			return true;
		},
		shadow: function(x, y, blur, color){
			this.ctx.shadowOffsetX = x;
			this.ctx.shadowOffsetY = y;
			this.ctx.shadowBlur    = blur;
			this.ctx.shadowColor   = color;
		},
		cleanShadow: function(){
			this.shadow(0, 0, 0, '#FFF');
		},
		getGradient: function(sx, sy, dx, dy, colors){
			var gradient = this.ctx.createLinearGradient(sx, sy, dx, dy);
			for (var i = 0, ilen=colors.length; i < ilen; i++) {
				gradient.addColorStop(i * 1/2, colors[i]);
			}
			return gradient;
		},
		/*
		** @int : sx, sy, dx, dy
		** @array(startColor, endColor) 
		** @bool
		*/
		shadowBackground: function(sx, sy, dx, dy, colors, shadow, shadowcolor){
			this.ctx.fillStyle = this.getGradient(sx, sy, dx, dy, colors);
			if(shadow){
				this.shadow(3, 0, 4, shadowcolor);
			}
			this.ctx.fillRect(sx, sy, dx, dy);
		},
		fillQuadrangle: function(x,y,width,height,offset, gradient, gradientColors){
			if(gradient){
				this.ctx.fillStyle = this.getGradient(x-offset, y+height, x+width-offset, y+height, gradientColors);
			}else{
				this.ctx.fillStyle = '#000000';
			}
			this.ctx.beginPath();
			this.ctx.moveTo(x+width, y);  
			this.ctx.lineTo(x+width-offset, y+height);  
			this.ctx.lineTo(x-offset, y+height);
			this.ctx.lineTo(x, y);
			this.ctx.lineTo(x+width, y);
			this.ctx.fill();
			this.ctx.closePath();
		},
		/*
		**
		*/
		drawSquareness: function(x,y,width,height, offset, color){
			if(jQuery.isArray(color)){
				this.ctx.fillStyle = this.getGradient(x-offset, y+height, x+width-offset, y+height, color);
			}else{
				this.ctx.fillStyle = color || '#000000';
			}
			this.ctx.beginPath();
			this.ctx.moveTo(x+width, y);  
			this.ctx.lineTo(x+width-offset, y+height);  
			this.ctx.lineTo(x-offset, y+height);
			this.ctx.lineTo(x, y);
			this.ctx.lineTo(x+width, y);
			this.ctx.fill();
			this.ctx.closePath();
		},
		drawSquare: function(x,y,width,height,color){
			this.drawSquareness(x,y,width,height, 0,color);
		},
		drawPercentLine: function(x,y, width, height, dy, n, units){
			var max = 1000, txt, m, x, y, units=units?units:'%',n=n?n:5;
			for(var i = 0; i<n+1; i++){
				txt = 100 - i*20;
				y = 40 + i*46;
				x = 10;
				this.font(txt+units, 20, y, {color:'#48505A', font: '10px arial', align:'right'});
				if(i < n+1){
					this.ctx.fillStyle = "rgb(124,137,149)";
					this.ctx.fillRect (22, y, 500, 1);
				}
			}
		},
		drawLine: function(x,y, tx,ty){
			this.ctx.fillStyle = "rgb(124,137,149)";
			this.ctx.fillRect (x, y, tx, ty);
		},
		drawCurveLine: function(xys, width, color){
			this.ctx.beginPath();
			for(var i =0,ilen=xys.length; i<ilen; i++){
				var mx=xys[i][0], my=xys[i][1];
				if(i==0){
					this.ctx.moveTo(mx, my); 
				}else{
					this.ctx.lineTo(mx, my); 
				}
			}
			this.ctx.lineWidth = width;
			this.ctx.strokeStyle = color;
			this.ctx.stroke();
			this.ctx.closePath();
		},
		font: function(txt, x,y, style){
			style = style || {};
			if(style.along && this.ctx.mozTextAlongPath){
				this.ctx.beginPath();
				var len = this.ctx.mozMeasureText(txt);
				this.ctx.moveTo(x,y);
				this.ctx.lineTo(x+10, y+len+6);
				this.ctx.fill();
				this.ctx.fillStyle = style.color || '#FFF';
				this.ctx.mozTextStyle = style.font || '12px arial';
				this.ctx.mozTextAlongPath(txt, false);
				this.ctx.closePath();
			}else{
				this.ctx.fillStyle    = style.color || '#FFF';
				this.ctx.font         = style.font || '12px Arial';
				this.ctx.textBaseline = style.valign || 'middle';
				this.ctx.textAlign	= style.align || 'center';
				this.ctx.fillText  (txt, x, y);
			}
		},
		drawCircle : function(x,y,radius, width,color,startAngle, endAngle){
			this.ctx.beginPath();
			this.ctx.arc(x, y, radius, 
				startAngle || 0, 
				endAngle || Math.PI*2
				, true); 
			this.ctx.strokeStyle = color || '#FFFFFF';
			this.ctx.lineWidth = width || 3;
			this.ctx.stroke();
			this.ctx.closePath();
		},
		drwaCircleChart: function(x,y,radius,datas){
			var x = x, y=y, radius=radius, total = eval(datas.data.join('+')),start=0,end=0,vtotal=0, colors=this._colors();
			for(var i in datas.data){
				var mx,my, mangle,mpercent=0, data = datas.data[i], color = datas.color[i] || colors[i], capture = datas.capture[i] || '';
				vtotal += data;
				start = datas.data.length == 1 ? Math.PI*2 : end;
				end = Math.PI*2*(1- vtotal/total);
				mpercent = parseInt(data/total*1000, 10)/10;
				mangle = parseInt(360*(vtotal - data/2)/total, 10);
				if(total==0 || data == total){
					start = Math.PI*2;
					end = 0;
					mpercent = 100;
					mangle = 180;
				}
				
				mx = x + ( (radius+10)*Math.cos(mangle/57.3) );
				my = y - ( (radius+10)*Math.sin(mangle/57.3) );
				if(data > 0 || (i == datas.data.length - 1 && total==0) ){
					this.ctx.beginPath();
					this.ctx.arc(x, y, radius, start, end, true);
					this.ctx.lineTo(x,y);
					this.ctx.strokeStyle = color;
					this.ctx.stroke();
					this.ctx.fillStyle = color;
					this.ctx.fill();
					this.ctx.closePath();
				}
					
				if(this.asdf){
					
				}else{
					this.drawSquare(x+radius+30, y-radius+i*20, 10,10, color);
					var m = function(num){
						var s = [];
						while(num>1000){
							s[s.length] = num.toString().substr(-3, 3);
							num = parseInt(num/1000, 10);
						}
						s[s.length] = num;
						return s.reverse().join(',');
					}(data);
					this.font(capture+' ('+m+' - '+mpercent+'%)', x+radius+46, y-radius+5+i*20, {color:color, align:'left'});
				}
				this.font(mpercent+'%', mx, my, {color:color, align:this._getAlignByCircleAngle(mangle)});
			}
		},

        /**
         * Converts 0-1 to 0-255
         * @method real2dec
         * @param n {float} the number to convert
         * @return {int} a number 0-255
         */
        real2dec: function(n) {
            return Math.min(255, Math.round(n*256));
        },

        /**
         * Converts HSV (h[0-360], s[0-1]), v[0-1] to RGB [255,255,255]
         * @method hsv2rgb
         * @param h {int|[int, float, float]} the hue, or an
         *        array containing all three parameters
         * @param s {float} the saturation
         * @param v {float} the value/brightness
         * @return {[int, int, int]} the red, green, blue values in
         *          decimal.
         */
        hsv2rgb: function(h, s, v) { 

            if (isArray(h)) {
                return this.hsv2rgb.call(this, h[0], h[1], h[2]);
            }

            var r, g, b,
                i = Math.floor((h/60)%6),
                f = (h/60)-i,
                p = v*(1-s),
                q = v*(1-f*s),
                t = v*(1-(1-f)*s),
                fn;

            switch (i) {
                case 0: r=v; g=t; b=p; break;
                case 1: r=q; g=v; b=p; break;
                case 2: r=p; g=v; b=t; break;
                case 3: r=p; g=q; b=v; break;
                case 4: r=t; g=p; b=v; break;
                case 5: r=v; g=p; b=q; break;
            }

            fn=this.real2dec;

            return [fn(r), fn(g), fn(b)];
        },

        /**
         * Converts to RGB [255,255,255] to HSV (h[0-360], s[0-1]), v[0-1]
         * @method rgb2hsv
         * @param r {int|[int, int, int]} the red value, or an
         *        array containing all three parameters
         * @param g {int} the green value
         * @param b {int} the blue value
         * @return {[int, float, float]} the value converted to hsv
         */
        rgb2hsv: function(r, g, b) {

            if (isArray(r)) {
                return this.rgb2hsv.apply(this, r);
            }

            r /= 255;
            g /= 255;
            b /= 255;

            var h,s,
                min = Math.min(Math.min(r,g),b),
                max = Math.max(Math.max(r,g),b),
                delta = max-min,
                hsv;

            switch (max) {
                case min: h=0; break;
                case r:   h=60*(g-b)/delta; 
                          if (g<b) {
                              h+=360;
                          }
                          break;
                case g:   h=(60*(b-r)/delta)+120; break;
                case b:   h=(60*(r-g)/delta)+240; break;
            }
            
            s = (max === 0) ? 0 : 1-(min/max);

            hsv = [Math.round(h), s, max];

            return hsv;
        },

        /**
         * Converts decimal rgb values into a hex string
         * 255,255,255 -> FFFFFF
         * @method rgb2hex
         * @param r {int|[int, int, int]} the red value, or an
         *        array containing all three parameters
         * @param g {int} the green value
         * @param b {int} the blue value
         * @return {string} the hex string
         */
        rgb2hex: function(r, g, b) {
            if (isArray(r)) {
                return this.rgb2hex.apply(this, r);
            }

            var f=this.dec2hex;
            return f(r) + f(g) + f(b);
        },
     
        /**
         * Converts an int 0...255 to hex pair 00...FF
         * @method dec2hex
         * @param n {int} the number to convert
         * @return {string} the hex equivalent
         */
        dec2hex: function(n) {
            n = parseInt(n,10)|0;
            n = (n > 255 || n < 0) ? 0 : n;

            return (ZERO+n.toString(16)).slice(-2).toUpperCase();
        },

        /**
         * Converts a hex pair 00...FF to an int 0...255 
         * @method hex2dec
         * @param str {string} the hex pair to convert
         * @return {int} the decimal
         */
        hex2dec: function(str) {
            return parseInt(str,16);
        },

        /**
         * Converts a hex string to rgb
         * @method hex2rgb
         * @param str {string} the hex string
         * @return {[int, int, int]} an array containing the rgb values
         */
        hex2rgb: function(s) { 
            var f = this.hex2dec;
            return [f(s.slice(0, 2)), f(s.slice(2, 4)), f(s.slice(4, 6))];
        },

        /**
         * Returns the closest websafe color to the supplied rgb value.
         * @method websafe
         * @param r {int|[int, int, int]} the red value, or an
         *        array containing all three parameters
         * @param g {int} the green value
         * @param b {int} the blue value
         * @return {[int, int, int]} an array containing the closes
         *                           websafe rgb colors.
         */
        websafe: function(r, g, b) {

            if (isArray(r)) {
                return this.websafe.apply(this, r);
            }

            // returns the closest match [0, 51, 102, 153, 204, 255]
            var f = function(v) {
                if (isNumber(v)) {
                    v = Math.min(Math.max(0, v), 255);
                    var i, next;
                    for (i=0; i<256; i=i+51) {
                        next = i+51;
                        if (v >= i && v <= next) {
                            return (v-i > 25) ? next : i;
                        }
                    }
                }

                return v;
            };

            return [f(r), f(g), f(b)];
        },
        length2width: function(length){
        	return length*6;
        },
        strlength: function(str){
        	return str.toString().replace(/[^\x00-\xff]/g,"xx").length;
        }
    };

})();


(function(){
	TVB.util.namespace('TVB.canvasLine');
	TVB.canvasCurveLines = function(el, options){
		var timer, avg, p = 3.1415926, labels=this._labels, colors=this._colors();
		if(!this.init(el, options)){
			return false;
		}
		var X=32, Y=20, tx=this.width-110,ty=270;
		this.shadowBackground(X, Y, tx+10, ty-Y +5, ['rgb(175,178,183)', 'rgb(229,230,234)'], 1, 'rgb(165, 168, 172)');
		this.cleanShadow();
		
		var mtx = 0;
		for(var i in options.datas){
			var item = options.datas[i], m;
			m = Math.max.apply( Math, item.data );
			mtx = Math.max(mtx,m);
		}
		
		var txt,y=Y+20,md=mtx/5;
		for(var i=0; i<5+1; i++){
			txt = parseInt((mtx - i*md)*10, 10)/10;
			txt = parseInt((mtx - i*md), 10);
			y = 40 + i*46;
			if(mtx > 10){
				this.font(txt, X-2, y, {color:'#48505A', font: '10px arial', align:'right'});
			}else{
				if(i==0 || i==5){
					this.font(txt, X-2, y, {color:'#48505A', font: '10px arial', align:'right'});
				}
			}
			if(i < 5+1){
				this.drawLine(X,y,tx,1);
			}
		}
		
		//25, 40, 500, 270
		for(var i in options.datas){
			var item = options.datas[i], color = colors[i];
			var x=X+8,y=Y+20, data=item.data,len=data.length, total=eval(item.data.join('+')),
				dx=len > 1 ? (tx-x)/(len-1) : x;
			var xys =[];
			for(var j =0; j<len; j++){
				var mx = x + j*dx;
				my = mtx ? ty - (ty-y)*data[j]/mtx : ty;
				xys[j] = [mx,my];
				if(i==0){
					this.font(this.labels[j], mx, 285, {color: color, along:1});
				}
				this.drawCircle(mx,my,2,2, color);
			}
			this.drawCurveLine(xys, 2, color);
			this.drawSquare(X+tx+20, 36+i*20, 10,10, color);
			this.font(item.capture, X+tx+36, 42+i*20, {color:color, align:'left'});
		}
	};
	jQuery.extend(TVB.canvasCurveLines, TVB.canvas);
	
	TVB.canvasColumns = function(options){
		var X=0,Y=20, tx,ty,maxData = 0, captureMaxLength =0, labelMaxLength =0
			colwidth = 7, colspace=7;
		if(options.title){
			Y=50;
		}
		for(var i in options.datas){
			for(var j in options.datas[i]){
				maxData = Math.max( maxData, options.datas[i][j]);
			}
		}
		X = this.length2width(maxData.toString().length)+10;
		for(var i in options.captures){
			captureMaxLength = Math.max( captureMaxLength, this.strlength(options.captures[i]) );
		}
		for(var i in options.labels){
			labelMaxLength = Math.max( labelMaxLength, this.strlength(options.labels[i]) );
		}
		if(options.refEl){
			this.width = X+this.length2width(captureMaxLength)+ options.labels.length*(options.captures.length*colwidth+colspace*2)+options.labels.length+1;
			this.height = Y+this.length2width(labelMaxLength)+300;
			if(this.width < options.labels.length*30){
				this.width = X+options.labels.length*30 + this.length2width(captureMaxLength);
			}
			var el = $('<canvas id="'+TVB.util.genId('columns_')+'" width="'+this.width+'" height="'+this.height+'"></canvas>');
			el.insertBefore( options.refEl );
			this.el = el;
		}
		tx = this.width - X - this.length2width(captureMaxLength) - 30;
		ty = this.height - Y - this.length2width(labelMaxLength) - 10;
		
		if(!this.init(this.el, options)){
			return false;
		}
		this.shadowBackground(X, Y, tx, ty, ['rgb(175,178,183)', 'rgb(229,230,234)'], 1, 'rgb(165, 168, 172)');
		this.cleanShadow();
		
		if(options.title){
			var mx = this.width/2;
			this.font(options.title, mx, 30, {color:'#48505A', font: '16px arial', align:'center'});
		}
		var txt,y,md=maxData/5;
		for(var i=0; i<5+1; i++){
			txt = maxData>10 ? parseInt((maxData - i*md), 10) : parseInt((maxData - i*md)*10, 10)/10;
			y = Y+20 + i*(ty-20)/5;
			this.font(txt, X-2, y, {color:'#48505A', font: '10px arial', align:'right'});
			this.drawLine(X,y,tx,1);
		}
		var dx=tx/options.labels.length,txt,mx;
		for(var i=0,ilen=options.labels.length; i<=ilen; i++){
			this.drawCircle(X+i*dx,Y+ty,1, 1,'#48505A');
			if(i<ilen){
				txt = options.labels[i];
				mx = X+i*dx+ dx/2 - this.length2width( this.strlength(txt) )/2;
				this.font(txt, mx, Y+ty+12, {color:'#48505A', font: '10px arial', align:'left'});
			}
		}
		var txt,color;
		for(var i=0,ilen=options.captures.length; i<ilen; i++){
			color = options.colors && options.colors[i] ? options.colors[i] : this.__colors(i);
			this.drawSquare(X+tx+10, Y+i*20, 10,10, color);
			txt = options.captures[i];
			this.font(txt, X+tx+25, Y+i*20+6, {color:'#48505A', align:'left'});
		}
		var color,mx,my,h,dx=tx/options.datas.length, colx=0;
		for(var i=0,ilen=options.datas.length; i<ilen; i++){
			for(var j=0,jlen=options.datas[i].length; j<jlen; j++){
				color = options.colors && options.colors[j] ? options.colors[j] : this.__colors(j);
				h = maxData==0 ? 0 :(ty-20)*options.datas[i][j]/maxData;
				mx = X+colwidth+1 + i*dx + j*colwidth;
				my = Y+ty-h;
				this.drawSquare(mx, my, colwidth,h, color);
			}
		}
	};
	jQuery.extend(TVB.canvasColumns, TVB.canvas);
	
	TVB.canvasColumn3d = function(el, options){
		if(!this.init(el, options)){
			return false;
		}
		this.shadowBackground(100, 20, 506, 254, ['rgb(175,178,183)', 'rgb(229,230,234)'], 1, 'rgb(165, 168, 172)');
		this.cleanShadow();
		this.drawPercentLine();
		
		this.fillQuadrangle(100, 273, 506, 5, 5, 1, ['rgb(175,178,183)', 'rgb(229,230,234)']);
		this.fillQuadrangle(100, 273, 506, 11, 11, 1, ['rgb(175,178,183)', 'rgb(229,230,234)']);
		this.ctx.fillStyle = this.getGradient(90, 283, 90, 293, ['rgb(189,196,201)', 'rgb(106,115,122)']);
		this.ctx.fillRect(90, 283, 506, 10);
	};
	jQuery.extend(TVB.canvasColumn3d, TVB.canvas);
	
	TVB.canvasCircleChart = function(el, options){
		if(!this.init(el, options)){
			return false;
		}
		
		var x = options.x, y=options.y, radius=options.radius, total = eval(options.datas.data.join('+')),start=0,end=0,vtotal=0;
		
		this.drwaCircleChart(x,y,radius, options.datas);
		this.font(options.capture, x-radius, y-radius-30, {color:'#FF0000',align:'left',font:'14px Arial'});
	};
	jQuery.extend(TVB.canvasCircleChart, TVB.canvas);
})();

